package jwt

import (
	"errors"
	"github.com/gin-gonic/gin"
	jwtpkg "github.com/golang-jwt/jwt"
	"strings"
	"time"
)

var (
	ErrTokenExpired           error = errors.New("令牌已过期")
	ErrTokenExpiredMaxRefresh error = errors.New("令牌已过最大刷新时间")
	ErrTokenMalformed         error = errors.New("请求令牌格式有误")
	ErrTokenInvalid           error = errors.New("请求令牌无效")
	ErrHeaderEmpty            error = errors.New("需要认证才能访问！")
	ErrHeaderMalformed        error = errors.New("请求头中 Authorization 格式有误")
)

const APP_NAME = "gfyx"                                         // app 名称
const DEFAULT_SIGN_KEY = "7264daa3-0cdb-11ed-8246-043f72e4f15a" // 默认签名 key
const MAX_REFRESH_TIME = 86400                                  // 默认 1个月
const EXPIRE_TIME = 10080                                       // token 有效时间

// JWTNew 定义一个jwt对象
type JWTNew struct {

	// 秘钥，用以加密 JWT，读取配置信息 app.key
	SignKey []byte

	// 刷新 Token 的最大过期时间
	MaxRefresh time.Duration
}

// TenantUserCustomClaims 自定义载荷
type TenantUserCustomClaims struct {
	Account    string `json:"account"`
	TenantCode string `json:"tenant_code"`
	AppName    string `json:"app_name"`

	jwtpkg.StandardClaims
}

func NewJWT() *JWTNew {
	return &JWTNew{
		SignKey:    []byte(DEFAULT_SIGN_KEY),
		MaxRefresh: time.Duration(MAX_REFRESH_TIME) * time.Minute,
	}
}

// ParserToken 解析 Token，中间件中调用
func (jwt *JWTNew) ParserToken(c *gin.Context) (*TenantUserCustomClaims, error) {
	tokenString := c.DefaultQuery("access_token", "")
	if tokenString == "" {
		tokenStringFromHeader, parseErr := jwt.getTokenFromHeader(c)
		if parseErr != nil {
			return nil, parseErr
		}
		tokenString = tokenStringFromHeader
	}

	// 1. 调用 jwt 库解析用户传参的 Token
	token, err := jwt.parseTokenString(tokenString)

	// 2. 解析出错
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		if ok {
			if validationErr.Errors == jwtpkg.ValidationErrorMalformed {
				return nil, ErrTokenMalformed
			} else if validationErr.Errors == jwtpkg.ValidationErrorExpired {
				return nil, ErrTokenExpired
			}
		}
		return nil, ErrTokenInvalid
	}

	// 3. 将 token 中的 claims 信息解析出来和 JWTCustomClaims 数据结构进行校验
	if claims, ok := token.Claims.(*TenantUserCustomClaims); ok && token.Valid {
		return claims, nil
	}

	return nil, ErrTokenInvalid
}

// RefreshToken 更新 Token，用以提供 refresh token 接口
func (jwt *JWTNew) RefreshToken(c *gin.Context) (string, error) {
	//

	// 1. 从 Header 里获取 token
	tokenString, parseErr := jwt.getTokenFromHeader(c)
	if parseErr != nil {
		return "", parseErr
	}

	// 2. 调用 jwt 库解析用户传参的 Token
	token, err := jwt.parseTokenString(tokenString)

	// 3. 解析出错，未报错证明是合法的 Token（甚至未到过期时间）
	if err != nil {
		validationErr, ok := err.(*jwtpkg.ValidationError)
		// 满足 refresh 的条件：只是单一的报错 ValidationErrorExpired
		if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired {
			return "", err
		}
	}

	// 4. 解析 JWTCustomClaims 的数据
	claims := token.Claims.(*TenantUserCustomClaims)

	// 5. 检查是否过了『最大允许刷新的时间』
	x := jwt.timenowInTimezone().Add(-jwt.MaxRefresh).Unix()
	if claims.IssuedAt > x {
		// 修改过期时间
		claims.StandardClaims.ExpiresAt = jwt.expireAtTime()
		return jwt.createToken(*claims)
	}

	return "", ErrTokenExpiredMaxRefresh
}

// IssueToken 生成  Token，在登录成功时调用
func (jwt *JWTNew) IssueToken(appName, account, tenantCode string) string {

	// 1. 构造用户 claims 信息(负荷)
	expireAtTime := jwt.expireAtTime()
	claims := TenantUserCustomClaims{
		Account:    account,
		AppName:    appName,
		TenantCode: tenantCode,
		StandardClaims: jwtpkg.StandardClaims{
			NotBefore: jwt.timenowInTimezone().Unix(), // 签名生效时间
			IssuedAt:  jwt.timenowInTimezone().Unix(), // 首次签名时间（后续刷新 Token 不会更新）
			ExpiresAt: expireAtTime,                   // 签名过期时间
			Issuer:    APP_NAME,                       // 签名颁发者
		},
	}

	// 2. 根据 claims 生成token对象
	token, err := jwt.createToken(claims)
	if err != nil {
		return ""
	}

	return token
}

// createToken 创建 Token，内部使用，外部请调用 IssueToken
func (jwt *JWTNew) createToken(claims TenantUserCustomClaims) (string, error) {
	// 使用HS256算法进行token生成
	token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
	return token.SignedString(jwt.SignKey)
}

// expireAtTime 过期时间
func (jwt *JWTNew) expireAtTime() int64 {
	timenow := jwt.timenowInTimezone()

	expire := time.Duration(EXPIRE_TIME) * time.Minute
	return timenow.Add(expire).Unix()
}

// parseTokenString 使用 jwtpkg.ParseWithClaims 解析 Token
func (jwt *JWTNew) parseTokenString(tokenString string) (*jwtpkg.Token, error) {
	return jwtpkg.ParseWithClaims(tokenString, &TenantUserCustomClaims{}, func(token *jwtpkg.Token) (interface{}, error) {
		return jwt.SignKey, nil
	})
}

// getTokenFromHeader 使用 jwtpkg.ParseWithClaims 解析 Token
// Authorization:Bearer xxxxx
func (jwt *JWTNew) getTokenFromHeader(c *gin.Context) (string, error) {
	authHeader := c.Request.Header.Get("Authorization")
	if authHeader == "" {
		return "", ErrHeaderEmpty
	}
	// 按空格分割
	parts := strings.SplitN(authHeader, " ", 2)
	if !(len(parts) == 2 && parts[0] == "Bearer") {
		return "", ErrHeaderMalformed
	}
	return parts[1], nil
}

func (jwt *JWTNew) timenowInTimezone() time.Time {
	chinaTimezone, _ := time.LoadLocation("Asia/Shanghai")
	return time.Now().In(chinaTimezone)
}
